import functools
import inspect
import threading
import time
import uuid
import traceback
from flask import jsonify, Flask, request
import requests
try:
    from .data import qualify
    from .type_schema import TypeSchema
    from .task import SymbolicTask
except ImportError:
    from data import qualify
    from type_schema import TypeSchema
    from task import SymbolicTask
def instant_req(url, inputs):
    return requests.post(url, json=inputs).json()["instant_result"]
task_status = {}
def desc(app):
    def decorator(func):
        @app.route('/', endpoint=f'{func.__name__}_index')
        @functools.wraps(func)
        def wrapper():
            return func()
        return wrapper
    return decorator
def meta(app):
    def decorator(func):
        @app.route('/meta', endpoint=f'{func.__name__}_meta')
        @functools.wraps(func)
        def wrapper():
            name, desc, category = func()
            return jsonify({
                'name': name,
                'desc': desc,
                'category': category
            })
        return wrapper
    return decorator
def tasks(app):
    def decorator(func):
        @app.route('/tasks', endpoint=f'{func.__name__}_tasks')
        @functools.wraps(func)
        def wrapper():
            tasks = func()
            return jsonify({
                'tasks': tasks
            })
        return wrapper
    return decorator
def workflows(app):
    def decorator(func):
        @app.route('/workflows', endpoint=f'{func.__name__}_workflows')
        @functools.wraps(func)
        def wrapper():
            workflows = func()
            return jsonify({
                'workflows': [workflow.jsonify() for workflow in workflows]
            })
        return wrapper
    return decorator
def instant_task(app):
    def decorator(func):
        @app.route(f'/task/{func.__name__}', endpoint=f'{func.__name__}_instant')
        @functools.wraps(func)
        def wrapper():
            res = func(request.json)
            return jsonify({
                "task_id": 0,
                "progress_pipe_addr": "",
                "result_addr": "",
                "instant_result": res
            })
        return wrapper
    return decorator
def qinstant_task(app, input_type: TypeSchema, output_type: TypeSchema):
    def decorator(func):
        @app.route(f'/task/{func.__name__}', endpoint=f'{func.__name__}_qinstant')
        @functools.wraps(func)
        def wrapper():
            data_in = request.json
            if not qualify(data_in, input_type):
                return jsonify({
                    "error": "invalid input"
                }), 400
            res = func(data_in)
            return jsonify({
                "task_id": "",
                "progress_pipe_addr": "",
                "result_addr": "",
                "instant_result": res
            })
        return wrapper
    return decorator
def qinstantq_task(app, input_type: TypeSchema, output_type: TypeSchema):
    def decorator(func):
        @app.route(f'/task/{func.__name__}', endpoint=f'{func.__name__}_qinstantq', methods=['POST'])
        @functools.wraps(func)
        def wrapper():
            data_in = request.json
            if not qualify(data_in, input_type):
                return jsonify({
                    "error": "invalid input"
                }), 400
            res = func(data_in)
            if not qualify(res, output_type):
                return jsonify({
                    "error": "invalid output"
                }), 500
            return jsonify({
                "task_id": "",
                "progress_pipe_addr": "",
                "result_addr": "",
                "instant_result": res
            })
        return wrapper
    return decorator
def instant(func):
    func._is_instant_task = True
    return func
def edp_service(cls):
    app = Flask(cls.__name__)
    cls.app = app
    def _update_progress(task_id, percentage, status=None):
        global task_status
        if task_id in task_status:
            if percentage is not None:
                task_status[task_id]["percentage"] = percentage
            if status is not None:
                task_status[task_id]["status"] = status
            return True
        return False
    def update_progress(self, percentage, status=None):
        if not hasattr(self, 'task_id'):
            assert 0 == 1, "call `update_progress()` from unlinked environment"
        return _update_progress(self.task_id, percentage, status)
    def _log(task_id, message):
        global task_status
        if task_id in task_status:
            task_status[task_id]["log"].append(message)
            return True
        return False
    def log(self, message):
        if not hasattr(self, 'task_id'):
            assert 0 == 1, "call `log()` from unlinked environment"
        return _log(self.task_id, message)
    def _compete(task_id):
        _update_progress(task_id, 100.0, "Completed")
        global task_status
        task_status[task_id]["completed_at"] = time.time()
    def complete(self):
        if not hasattr(self, 'task_id'):
            assert 0 == 1, "call `complete()` from unlinked environment"
        _compete(self.task_id)
    def _fail(task_id, errmsg='unknown error'):
        _update_progress(task_id, 0.0, "Failed")
        global task_status
        task_status[task_id]["completed_at"] = time.time()
    def fail(self, errmsg='unknown error'):
        if not hasattr(self, 'task_id'):
            assert 0 == 1, "call `fail()` from unlinked environment"
        _fail(self, errmsg)
    cls.update_progress = update_progress
    @classmethod
    def _name(cls):
        if hasattr(cls, 'name') and isinstance(cls.name, str):
            return cls.name
        elif hasattr(cls, 'name') and callable(cls.name):
            return cls.name()
        else:
            return cls.__name__
    @classmethod
    def _desc(cls):
        if hasattr(cls, 'description') and isinstance(cls.description, str):
            return cls.description
        elif hasattr(cls, 'description') and callable(cls.description):
            return cls.description()
        elif hasattr(cls, 'desc') and isinstance(cls.desc, str):
            return cls.desc
        elif hasattr(cls, 'desc') and callable(cls.desc):
            return cls.desc()
        else:
            return cls.__name__
    @classmethod
    def _category(cls):
        if hasattr(cls, 'category') and isinstance(cls.category, str):
            return cls.category
        elif hasattr(cls, 'category') and callable(cls.category):
            return cls.category()
        else:
            return None
    @classmethod
    def _tasks_nominal(cls):
        if hasattr(cls, 'tasks') and isinstance(cls.tasks, list):
            return cls.tasks
        elif hasattr(cls, 'tasks') and callable(cls.tasks):
            return cls.tasks()
        else:
            return []
    @classmethod
    def _tasks_symbolic(cls):
        try:
            return [SymbolicTask.try_parse(task) for task in cls._tasks_nominal()]
        except Exception as e:
            print(f"task signiture parse error: {e}")
            return []
    @classmethod
    def _workflows(cls):
        if hasattr(cls, 'workflows') and isinstance(cls.workflows, list):
            return cls.workflows
        elif hasattr(cls, 'workflows') and callable(cls.workflows):
            return cls.workflows()
        else:
            return []
    cls._name = _name
    cls._desc = _desc
    cls._category = _category
    cls._tasks_nominal = _tasks_nominal
    cls._tasks_symbolic = _tasks_symbolic
    cls._workflows = _workflows
    @app.route('/', endpoint=f'{cls.__name__}_index')
    def index_route():
        return cls._desc()
    print('route: GET /')
    @app.route('/meta', endpoint=f'{cls.__name__}_meta')
    def meta_route():
        if hasattr(cls, 'meta') and isinstance(cls.meta, str):
            return cls.meta
        elif hasattr(cls, 'meta') and callable(cls.meta):
            return cls.meta()
        else:
            return jsonify({
                "name": cls._name(),
                "desc": cls._desc(),
                "category": cls._category()
            })
    print('route: GET /meta')
    @app.route('/tasks', endpoint=f'{cls.__name__}_tasks')
    def tasks_route():
        return jsonify(cls._tasks_nominal())
    print('route: GET /tasks')
    @app.route('/workflows', endpoint=f'{cls.__name__}_workflows')
    def workflows_route():
        return jsonify(cls._workflows())
    print('route: GET /workflows')
    @app.route('/mount', endpoint=f'{cls.__name__}_mount')
    def mount_route():
        return jsonify({
            "session_key": uuid.uuid4(),
            "healthy_info": "Healthy"
        })
    print('route: GET /mount')
    __tasks = cls._tasks_symbolic()
    __task_names = [task.name for task in __tasks]
    print('find ', len(__task_names), ' tasks: ', __task_names, ' in ', cls._name())
    all_methods = {}
    for name, attr in cls.__dict__.items():
        if not name.startswith('_') and name in __task_names:
            if isinstance(attr, classmethod):
                print(f'Should not be a class method: {name}')
                all_methods[name] = attr
            elif callable(attr):
                all_methods[name] = attr
            else:
                print(f'Not a method: {name}')
    missing_methods = [name for name in __task_names if name not in all_methods]
    if missing_methods:
        print(f"Warning: The following tasks have no corresponding methods in the class: {missing_methods}")
    for task_name in __task_names:
        if task_name not in all_methods:
            continue
        method_name = task_name
        method = getattr(cls, method_name)
        task_def = next((task for task in __tasks if task.name == method_name), None)
        if not task_def:
            print(f"Warning: Task '{method_name}' has no type definition, skipping route creation")
            continue
        try:
            sig = inspect.signature(method)
            param_count = len(sig.parameters)
            if param_count == 1:
                if task_def.input_type.kind != TypeSchema.Kind.VOID:
                    print(f"Error: Method '{task_name}' has only one parameter, but task signature input type is not Void but {task_def.input_type}")
                    print(f"      Please modify task signature to '{task_name}:Void->{task_def.output_type}' or add data parameter")
                    continue
        except Exception as e:
            print(f"Error getting method '{task_name}' signature: {e}")
            param_count = 2
        is_instant_task = False
        if hasattr(method, '_is_instant_task'):
            is_instant_task = True
        if is_instant_task:
            print(f"(instant) '{task_name}' {param_count} param(s), {task_def.input_type} -> {task_def.output_type}")
        else:
            print(f"'{task_name}' {param_count} param(s), {task_def.input_type} -> {task_def.output_type}")
        if is_instant_task:
            @app.route(f'/task/{task_name}', endpoint=f'task/{task_name}_instant', methods=['POST'])
            def instant_task_route(method=method, task_def=task_def, method_name=task_name, param_count=param_count):
                @functools.wraps(method)
                def wrapper():
                    if request.is_json:
                        input_data = request.json
                    elif task_def.input_type.kind == TypeSchema.Kind.VOID:
                        input_data = None
                    else:
                        return jsonify({
                            "error": "content-type must be application/json",
                        }), 400
                    try:
                        if not qualify(input_data, task_def.input_type):
                            return jsonify({
                                "error": "Input data type mismatch",
                                "expected": str(task_def.input_type),
                                "received": str(type(input_data))
                            }), 400
                    except Exception as e:
                        return jsonify({
                            "error": f"Error validating input data: {str(e)}"
                        }), 400
                    task_id = f"instant_{str(uuid.uuid4())}"
                    instance = cls()
                    cls.task_id = task_id
                    try:
                        if param_count == 1:
                            res = method(instance)
                        elif param_count == 2:
                            res = method(instance, input_data)
                        try:
                            if not qualify(res, task_def.output_type):
                                print(f"Warning: Task '{method_name}' return value type does not match signature")
                                print(f"      Expected: {task_def.output_type}")
                                print(f"      Actual: {type(res)}")
                                return jsonify({
                                    "error_message": f"Task '{method_name}' return value type does not match signature, expected: {task_def.output_type}, actual: {type(res)}",
                                    "status": "Failed",
                                    "instant_result": None
                                }), 400
                        except Exception as e:
                            print(f"Warning: Error validating output type - {str(e)}")
                        return jsonify({
                            "task_id": "",
                            "progress_pipe_addr": "",
                            "result_addr": "",
                            "status": "Completed",
                            "instant_result": res,
                        }), 200
                    except Exception as e:
                        stack_trace = traceback.format_exc()
                        print(f"Instant task '{method_name}' execution error:\n{stack_trace}")
                        return jsonify({
                            "task_id": "",
                            "progress_pipe_addr": "",
                            "result_addr": "",
                            "status": "Failed",
                            "instant_result": None,
                            "error_message": str(e),
                        }), 500
                return wrapper()
            print(f'route: POST /task/{task_name}')
        else:
            @app.route(f'/task/{task_name}', methods=['POST'], endpoint=f'task/{task_name}_start')
            def task_start_route(method=method, method_name=task_name, task_def=task_def, param_count=param_count):
                @functools.wraps(method)
                def wrapper():
                    try:
                        if request.is_json:
                            data = request.json
                        elif task_def.input_type.kind == TypeSchema.Kind.VOID:
                            data = None
                        else:
                            return jsonify({
                                    "error": "content-type must be application/json",
                                }), 400
                        try:
                            if not qualify(data, task_def.input_type):
                                return jsonify({
                                    "error": "Input data type mismatch",
                                    "expected": str(task_def.input_type),
                                    "received": str(type(data))
                                }), 400
                        except Exception as e:
                            return jsonify({
                                "error": f"Error validating input data: {str(e)}"
                            }), 400
                        task_id = str(uuid.uuid4())
                        global task_status
                        task_status[task_id] = {
                            "status": "Created",
                            "percentage": 0.0,
                            "method": method_name,
                            "created_at": time.time(),
                            "completed_at": None,
                            "log": [],
                            "log_cursor": 0,
                            "result": None,
                            "error_message": None,
                            "stack_trace": None
                        }
                        def run_task(task_id):
                            global task_status
                            try:
                                _update_progress(task_id, 0.0, "Running")
                                instance = cls()
                                instance.task_id = task_id
                                if param_count == 1:
                                    result = method(instance)
                                else:
                                    result = method(instance, data)
                                try:
                                    if not qualify(result, task_def.output_type):
                                        print(f"Warning: Task '{method_name}' return value type does not match signature")
                                        print(f"      Expected: {task_def.output_type}")
                                        print(f"      Actual: {type(result)}")
                                except Exception as e:
                                    print(f"Warning: Error validating output type - {str(e)}")
                                _update_progress(task_id, 100.0, "Completed")
                                task_status[task_id]["result"] = result
                                task_status[task_id]["completed_at"] = time.time()
                            except Exception as e:
                                stack_trace = traceback.format_exc()
                                print(f"Task '{method_name}' execution error:\n{stack_trace}")
                                error_msg = str(e)
                                _update_progress(task_id, None, "Failed")
                                task_status[task_id]["error"] = error_msg
                                task_status[task_id]["stack_trace"] = stack_trace
                                task_status[task_id]["completed_at"] = time.time()
                        
                        thread = threading.Thread(target=run_task, args=[task_id])
                        thread.daemon = True
                        thread.start()
                        return jsonify({
                            "task_id": task_id,
                            "progress_pipe_addr": f"/task/{method_name}/progress/{task_id}",
                            "result_addr": f"/task/{method_name}/result/{task_id}"
                        })
                    except Exception as e:
                        stack_trace = traceback.format_exc()
                        print(f"Error creating task:\n{stack_trace}")
                        return jsonify({
                            "error_message": str(e),
                            "stack_trace": stack_trace if app.debug else None
                        }), 500
                return wrapper()
            print(f'route: POST /task/{task_name}')
            @app.route(f'/task/{task_name}/progress/<task_id>', methods=['GET'], endpoint=f'task/{task_name}_progress')
            def task_progress_route(task_id):
                @functools.wraps(method)
                def wrapper(task_id):
                    global task_status
                    if task_id not in task_status:
                        return jsonify({
                            "error": f"Task {task_id} does not exist"
                        }), 404
                    task_info = task_status[task_id]
                    return jsonify({
                        "status": task_info.get("status", "Unknown"),
                        "percentage": task_info.get("percentage", 0.0),
                    })
                return wrapper(task_id)
            print(f'route: GET /task/{task_name}/progress/<task_id>')
            @app.route(f'/task/{task_name}/result/<task_id>', methods=['GET'], endpoint=f'task/{task_name}_result')
            def task_result_route(task_id):
                @functools.wraps(method)
                def wrapper(task_id):
                    global task_status
                    if task_id not in task_status:
                        return jsonify({
                            "error": f"Task {task_id} does not exist"
                        }), 400
                    task_info = task_status[task_id]
                    status = task_info.get("status", "unknown")
                    if status == "Completed":
                        return jsonify(task_info.get("result", None))
                    elif status == "Failed":
                        response = {
                            "status": status,
                            "error_message": task_info.get("error_message", "Unknown error")
                        }
                        if app.debug and "stack_trace" in task_info:
                            response["stack_trace"] = task_info["stack_trace"]
                        return jsonify(response), 500
                    else:
                        return jsonify({
                            "status": status,
                        }), 202
                return wrapper(task_id)
            print(f'route: GET /task/{task_name}/result/<task_id>')
            @app.route(f'/task/{task_name}/log/<task_id>', methods=["GET"], endpoint=f"task/{task_name}_log")
            def task_log_route(task_id):
                @functools.wraps(method)
                def wrapper(task_id):
                    global task_status
                    if task_id not in task_status:
                        return jsonify({
                            "error": f"Task {task_id} does not exist"
                        }), 400
                    task_log = task_status[task_id]["log"]
                    task_log_cursor = task_status[task_id]["log_cursor"]
                    new_pos = len(task_log)
                    if new_pos > task_log_cursor:
                        new_log = task_log[task_log_cursor:new_pos]
                        task_status[task_id]["log_cursor"] = new_pos
                        return jsonify(new_log)
                    return jsonify([])
                return wrapper(task_id)
            print(f'route: GET /task/{task_name}/log/<task_id>')
    return cls